#include "ObjectPropertyBrowser.h"

#include "qtpropertybrowser/qttreepropertybrowser.h"
#include "qtpropertybrowser/qtvariantproperty.h"

#include <QDebug>
#include <QMetaObject>
#include <QMetaProperty>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QSplitter>
#include <QVBoxLayout>

using namespace LDO;

ObjectPropertyBrowser::ObjectPropertyBrowser(QWidget *parent)
    : QWidget(parent)
{
    m_propertyBrowser = new QtTreePropertyBrowser();
    m_propertyManager = new QtVariantPropertyManager(this);
    m_propertyEditorFactory = new QtVariantEditorFactory(this);
    m_propertyBrowser->setFactoryForManager(m_propertyManager, m_propertyEditorFactory);

    setLayout(new QVBoxLayout);
    layout()->addWidget(m_propertyBrowser);
    layout()->setMargin(0);
    layout()->setSpacing(0);
}

void ObjectPropertyBrowser::setObject(QObject *object)
{
    if (m_object != nullptr)
        unpopulateBrowser();

    m_object = object;

    if (m_object != nullptr)
    {
        connect(m_object, &QObject::destroyed,
                this, [this]() {
            unpopulateBrowser();
            m_object = nullptr;
        });
        populateBrowser();
    }
}

void ObjectPropertyBrowser::populateBrowser()
{
    populateBrowser(m_object->metaObject(), m_object);
    connect(m_propertyManager, &QtVariantPropertyManager::valueChanged,
            this, &ObjectPropertyBrowser::onManagerPropertyValueChanged);
}

void ObjectPropertyBrowser::populateBrowser(const QMetaObject *metaObject, QObject *object)
{
    if (metaObject->superClass() != nullptr)
        populateBrowser(metaObject->superClass(), object);

    auto managerGroupProperty = m_propertyManager->addProperty(QtVariantPropertyManager::groupTypeId(),
                                                               metaObject->className());

    for (int metaPropertyIndex = metaObject->propertyOffset();
         metaPropertyIndex < metaObject->propertyCount();
         metaPropertyIndex++)
    {
        QMetaProperty metaProperty = metaObject->property(metaPropertyIndex);

        if (!metaProperty.isDesignable() || !metaProperty.isReadable())
            continue;

        if (!metaProperty.hasNotifySignal() && !metaProperty.isConstant())
            continue;

        QVariant propertyValue;
        QtVariantProperty *managerProperty = nullptr;
        if (metaProperty.isFlagType())
        {
            QMetaEnum metaEnum = metaProperty.enumerator();
            managerProperty = m_propertyManager->addProperty(QtVariantPropertyManager::flagTypeId(),
                                                             metaProperty.name());
            QStringList names;
            for (int i =0; i<metaEnum.keyCount(); i++)
            {
                QString name = metaEnum.key(i);
                names.append(labelFromPropertyName(name));
            }
            m_propertyManager->setAttribute(managerProperty, "flagNames", names);
            propertyValue = QVariant(QVariant::Int, metaProperty.read(object).data());
        }
        else if (metaProperty.isEnumType())
        {
            QMetaEnum metaEnum = metaProperty.enumerator();
            managerProperty = m_propertyManager->addProperty(QtVariantPropertyManager::enumTypeId(),
                                                             metaProperty.name());
            QStringList names;
            for (int i =0; i<metaEnum.keyCount(); i++)
            {
                QString name = metaEnum.key(i);
                names.append(labelFromPropertyName(name));
            }
            m_propertyManager->setAttribute(managerProperty, "enumNames", names);
            bool ok = false;
            propertyValue = metaProperty.read(object).toInt(&ok);
        }
        else
        {
            managerProperty = m_propertyManager->addProperty(metaProperty.type(),
                                                             metaProperty.name());
            propertyValue = metaProperty.read(object);
        }
        if (managerProperty != nullptr)
        {
            managerProperty->setPropertyName(labelFromPropertyName(managerProperty->propertyName()));
            managerProperty->setValue(propertyValue);
            managerProperty->setEnabled(metaProperty.isWritable());
            managerGroupProperty->addSubProperty(managerProperty);            
            m_managerPropertyToObjectPropertyName.insert(managerProperty, metaProperty.name());

            QMetaMethod notifyMethod = metaProperty.notifySignal();
            m_objectSignalIndexToManagerProperty.insert(notifyMethod.methodIndex(), managerProperty);
            connect(object, "2" + notifyMethod.methodSignature(),
                    this, SLOT(onObjectPropertyValueChanged()));
        }
    }

    if (!managerGroupProperty->subProperties().isEmpty())
    {
        m_propertyBrowser->addProperty(managerGroupProperty);
    }
    else
        delete managerGroupProperty;
}

void ObjectPropertyBrowser::unpopulateBrowser()
{
    m_propertyManager->disconnect(this);
    if (m_object != nullptr)
        m_object->disconnect(this);
    m_propertyManager->clear();
    m_managerPropertyToObjectPropertyName.clear();
    m_objectSignalIndexToManagerProperty.clear();
}

void ObjectPropertyBrowser::onObjectPropertyValueChanged()
{
    int signalIndex = senderSignalIndex();
    if (!m_objectSignalIndexToManagerProperty.contains(signalIndex))
    {
        qWarning() << "DocumentWidget::onObjectPropertyValueChanged(): Received an unkown property notification";
        return;
    }
    QtProperty *managerProperty = m_objectSignalIndexToManagerProperty[signalIndex];
    if (!m_managerPropertyToObjectPropertyName.contains(managerProperty))
    {
        qWarning() << "DocumentWidget::onObjectPropertyValueChanged(): Received an unkown property notification";
        return;
    }
    const char *objectPropertyName = m_managerPropertyToObjectPropertyName[managerProperty];
    m_propertyManager->setValue(managerProperty, m_object->property(objectPropertyName));
}

void ObjectPropertyBrowser::onManagerPropertyValueChanged(QtProperty *property, const QVariant &value)
{
    if (!m_managerPropertyToObjectPropertyName.contains(property))
    {
        // Can be a sub property, eg. size property creates 2 sub properties "widht" and "height"
        for (const QtProperty *superProperty: m_managerPropertyToObjectPropertyName.keys())
            if (superProperty->subProperties().contains(property))
                return;
        qWarning() << "ObjectPropertyBrowser::onManagerPropertyValueChanged(): Received an unkown property notification: " << property->propertyName();
        return;
    }
    const char *objectPropertyName = m_managerPropertyToObjectPropertyName[property];
    m_object->setProperty(objectPropertyName, value);
    // This doesn't seem to be enough when the object's property haven't change
    // Maybe need to set the value on the browser?
    const QVariant actualValue = m_object->property(objectPropertyName);
    if (actualValue != value)
        m_propertyManager->setValue(property, actualValue);
}

QString ObjectPropertyBrowser::labelFromPropertyName(const QString &name)
{
    QRegularExpression regexp("^([A-Z]?[a-z]+)(.*)$");
    if (!regexp.isValid())
    {
        qDebug() << regexp.errorString();
        qDebug() << regexp.patternErrorOffset();
    }
    QStringList words;
    auto reMatch = regexp.match(name);
    auto word = reMatch.captured(1);
    word.replace(0, 1, word.at(0).toUpper());
    words.append(word);

    regexp.setPattern("([A-Z][a-z]*)");
    auto reIter = regexp.globalMatch(reMatch.captured(2));
    while (reIter.hasNext())
    {
        word = reIter.next().captured(1);
        word.replace(0, 1, word.at(0).toLower());
        words.append(word);
    }

    return words.join(' ');
}


void ObjectPropertyBrowser::focusInEvent(QFocusEvent *event)
{
    Q_UNUSED(event)

    if (m_propertyBrowser->topLevelItems().isEmpty())
        return;
    QtBrowserItem *item = m_propertyBrowser->topLevelItems().first();
    while (!item->children().isEmpty())
        item = item->children().first();
    m_propertyBrowser->setCurrentItem(item);
    m_propertyBrowser->editItem(item);
}
